﻿using System;
using System.Collections.Generic;

namespace Framework
{
    public class EventTarget : IEventTarget, IDisposable
    {
        private static ObjectPool<ETEvent> _EventPool = new ObjectPool<ETEvent>();

        private Dictionary<int, List<EventListener>> _eventType2listenersDict =
            new Dictionary<int, List<EventListener>>();

        private ETEvent _emittingEvent = null;
        private List<ETEvent> _toEmitEventList = new List<ETEvent>();

        public void AddListener(int type, Action<ETEvent> onEvent)
        {
            if (_emittingEvent == null || _emittingEvent.type != type)
            {
                DirectAddListener(type, onEvent);
            }
            else
            {
                EventListener listener = FindListener(type, onEvent);
                if (listener == null)
                {
                    DirectAddListener(type, onEvent);
                }
                else if (listener.IsUnused())
                {
                    listener.Retain();
                }
            }
        }

        public void RemoveListener(int type, Action<ETEvent> onEvent)
        {
            if (_emittingEvent == null || _emittingEvent.type != type)
            {
                DirectRemoveListener(type, onEvent);
            }
            else
            {
                EventListener listener = FindListener(type, onEvent);
                if (listener != null && !listener.IsUnused())
                {
                    listener.Release();
                }
            }
        }
        
        public void Emit(int type, params object[] args)
        {
            ETEvent emitEvent = _EventPool.Retain(this, type, args);
            if (_emittingEvent != null)
            {
                _toEmitEventList.Add(emitEvent);
                return;
            }

            Emit(emitEvent);
        }

        public virtual void Dispose()
        {
            _eventType2listenersDict.Clear();
            for (int i = 0; i < _toEmitEventList.Count; ++i)
            {
                _EventPool.Release(_toEmitEventList[i]);
            }
            _toEmitEventList.Clear();
        }

        private void DirectAddListener(int type, Action<ETEvent> onEvent)
        {
            EventListener listener = FindListener(type, onEvent);
            if (listener == null)
            {
                listener = new EventListener(type, onEvent);
                listener.Retain();
                List<EventListener> listenerList;
                if (!_eventType2listenersDict.TryGetValue(type, out listenerList))
                {
                    listenerList = new List<EventListener>();
                    _eventType2listenersDict.Add(type, listenerList);
                }
                listenerList.Add(listener);
            }
        }

        private void DirectRemoveListener(int type, Action<ETEvent> onEvent)
        {
            EventListener listener = FindListener(type, onEvent);
            if (listener != null)
            {
                listener.Release();
                List<EventListener> listenerList = _eventType2listenersDict[type];
                listenerList.Remove(listener);
            }
        }

        private EventListener FindListener(int type, Action<ETEvent> onEvent)
        {
            List<EventListener> listenerList;
            if (_eventType2listenersDict.TryGetValue(type, out listenerList))
            {
                for (int i = 0; i < listenerList.Count; ++i)
                {
                    EventListener listener = listenerList[i];
                    if (listener.Equals(onEvent))
                    {
                        return listener;
                    }
                }
            }
            return null;
        }

        private void Emit(ETEvent emitEvent)
        {
            _emittingEvent = emitEvent;
            List<EventListener> listenerList;
            if (_eventType2listenersDict.TryGetValue(emitEvent.type, out listenerList))
            {
                int count = listenerList.Count;
                for (int i = 0; i < count; ++i)
                {
                    EventListener listener = listenerList[i];
                    if (!listener.IsUnused())
                    {
                        listener.onEvent(emitEvent);
                    }
                }

                count = listenerList.Count;
                for (int i = count - 1; i >= 0; --i)
                {
                    EventListener listener = listenerList[i];
                    if (listener.IsUnused())
                    {
                        listenerList.RemoveAt(i);
                    }
                }
            }
            _EventPool.Release(emitEvent);
            _emittingEvent = null;
            if (_toEmitEventList.Count > 0)
            {
                ETEvent nextEmitEvent = _toEmitEventList[0];
                _toEmitEventList.RemoveAt(0);
                Emit(nextEmitEvent);
            }
        }
        
        protected class EventListener : ReferenceCountable
        {
            public int type;
            public Action<ETEvent> onEvent;

            public EventListener(int type, Action<ETEvent> onEvent)
            {
                this.type = type;
                this.onEvent = onEvent;
            }

            public bool Equals(Action<ETEvent> onEvent)
            {
                return this.onEvent == onEvent;
            }
        }
    }
}